;--- zansi.asm ----------------------------------------------------------
; Zephyr ANSI terminal driver.
;    Copyright (C) 1986-1987, Thomas Hanlin III, Alexandria VA.
;    Based on original code for NANSI by Daniel Kegel, Pasadena CA.
;------------------------------------------------------------------------

INCLUDE zansi_d.asm

        ; from zansi_f.asm
        extrn   f_escape:near, f_in_escape:near

        ; from zansi_p.asm
        extrn   param_end:word

        ; from zansi_i.asm
        extrn   dosfn0:near

        ; to zansi_p.asm
        public  f_loopdone
        public  f_not_ansi
        public  f_ansi_exit

        ; to both zansi_p.asm and zansi_f.asm
        public  cur_x, cur_y, max_x, cur_attrib

        ; to zansi_f.asm
        public  xy_to_regs, get_blank_attrib
        public  port_6845
        public  wrap_flag
        public  cur_parm_ptr
        public  cur_coords, saved_coords, max_y
        public  escvector, string_term
        public  cpr_esc, cprseq
        public  video_mode

        ; to zansi_i.asm
        public  req_ptr, break_handler
        public  int_29
	 if	  takeBIOS
	 public	new_vid_bios, old_vid_bios
	 endif

        ; to all modules

keybuf  struc                           ; Used in getchar
len     dw      ?
adr     dw      ?
keybuf  ends


ABS40   segment at 40h
        org     1ah
buffer_head     dw      ?       ; Used in 'flush input buffer' dos call.
buffer_tail     dw      ?

        org     49h
crt_mode        db      ?
crt_cols        dw      ?
crt_len         dw      ?
crt_start       dw      ?
cursor_posn     dw      8 dup (?)
cursor_mode     dw      ?
active_page     db      ?
addr_6845       dw      ?
crt_mode_set    db      ?       ; = 7 only if monochrome display adaptor
crt_palette     db      ?
        org     6ch
timer_low       dw      ?       ; low word of time-of-day counter (18.2 hz)

	 ORG	  84h
video_rows	  db	   ?
ABS40   ends


CODE    segment byte public 'CODE'
assume  cs:code, ds:code

        ; Device Driver Header

        org     0

        dd      -1                      ; next device
        dw      8013h                   ; attributes
        dw      strategy                ; request header pointer entry
        dw      interrupt               ; request entry point
        db      'CON', 5 dup(' ')       ; device name (8 char)


;----- variable area --------------------
req_ptr label   dword
req_off dw      ?
req_seg dw      ?

wrap_flag       db      1       ; 0 = no wrap past line end
escvector       dw      0       ; state vector of ESCape sequencer
video_mode      db      3       ; ROM BIOS video mode (2=BW, 3=color)
max_y           db      24
max_cur_x       label   word    ; used to get both max & cur at once
max_x           db      79      ; line width (79 for 80x25 modes)
cur_coords      label   word
cur_x           db      0       ; cursor position (0 = left edge)
cur_y           db      0       ;                 (0 = top edge)
saved_coords    dw      ?       ; holds XY after a SCP escape sequence
string_term     db      0       ; either escape or double quote
cur_attrib      db      7       ; current char attributes
cur_page        db      0       ; current display page
video_seg       dw      ?       ; segment of video card
f_cptr_seg      dw      ?       ; part of fastout write buffer pointer
cur_parm_ptr    dw      ?       ; last byte of parm area now used
port_6845       dw      ?       ; port address of 6845 card
		  if	   takeBIOS
old_vid_bios	  dd	   ?	    ; pointer to old video bios routine
		  endif

brkkeybuf       db      3       ; control C
fnkeybuf        db      ?       ; holds second byte of fn key codes
cpr_buf         db      8 dup (?), '['
cpr_esc         db      27      ; descending buffer for cpr function

; following four keybufs hold information about input
; Storage order determines priority- since the characters making up a function
; key code must never be separated (say, by a Control-Break), they have the
; highest priority, and so on.  Keyboard keys (except ctrl-break) have the
; lowest priority.

fnkey   keybuf  <0, fnkeybuf>   ; fn key string (0 followed by scan code)
cprseq  keybuf  <0>             ; CPR string (ESC [ y;x R)
brkkey  keybuf  <0, brkkeybuf>  ; ^C
xlatseq keybuf  <0>             ; keyboard reassignment string

;------ xy_to_regs --------------------------------------------
; on entry: x in cur_x, y in cur_y
; on exit:  dx = chars left on line, di = address
; Alters ax, bx.
xy_to_regs      proc    near
        ; Find number of chars til end of line, keep in DX
        mov     ax,max_cur_x
        mov     bl,ah
        xor     bh,bh
        cbw
        mov     dx,ax
        sub     dx,bx
        inc     dx                      ; DX is # of chars till EOL
        ; Calculate DI = current address in text buffer
        inc     ax                      ; AL = max_x
        mul     cur_y
        add     ax,bx                   ; AX is # of chars into buffer
        shl     ax,1
        mov     di,ax                   ; DI is now offset of cursor.
        ret
xy_to_regs      endp


;------- dos_fn_tab -------------
; This table is used in "interrupt" to call the routine that handles
; the requested function.

max_cmd equ     12
dos_fn_tab:
        dw      dosfn0, nopcmd, nopcmd, badcmd, dosfn4, dosfn5, dosfn6
        dw      dosfn7, dosfn8, dosfn8, nopcmd, nopcmd

;------- strategy ----------------------------------------------------
; DOS calls strategy with a request which is to be executed later.
; Strategy just saves the request.

strategy        proc    far
        mov     cs:req_off,BX
        mov     cs:req_seg,ES
        ret
strategy        endp

pushall MACRO
	 IF is_8088
        push    ax
        push    cx
        push    dx
        push    bx
        push    bp
        push    si
        push    di
	 ELSE
	 pusha
	 ENDIF
	 ENDM

popall	 MACRO
	 IF is_8088
        pop     di
        pop     si
        pop     bp
        pop     bx
        pop     dx
        pop     cx
        pop     ax
	 ELSE
	 popa
	 ENDIF
	 ENDM

;------ interrupt -----------------------------------------------------
; This is where the request handed us during "strategy" is
; actually carried out.
; Calls one of 12 subroutines depending on the function requested.
; Each subroutine returns with exit status in AX.

interrupt       proc    far
        sti
        pushall
	 push    ds
        push    es

	 assume  cs:code, ds:abs40
	 mov     ax, abs40
	 mov     ds, ax
	 mov     al, video_rows
	 mov     cs:max_y, al
	 assume  cs:code, ds:code

        ; Read requested function information into registers
        lds     bx,cs:req_ptr
        mov     al,2[BX]                ; al = function code
        les     si,14[BX]               ; ES:SI = input/output buffer addr
        mov     cx,18[BX]               ; cx = input/output byte count

        cmp     al,max_cmd
        ja      unk_command             ; too big, exit with error code

        mov     bx,ax
        shl     bx,1                    ; form index to table of words
        mov     ax,cs
        mov     ds,ax
        call    word ptr dos_fn_tab[bx]
int_done:
        lds     bx,cs:req_ptr           ; report status
        or      ax,100h                 ; (always set done bit upon exit)
        mov     3[bx],ax

        pop     ES                      ; restore caller's registers
        pop     DS
	 popall
	 ret                             ; return to DOS.

unk_command:
        call    badcmd
        jmp     int_done

interrupt       endp

;----- BIOS break handler -----------------------------------------
; Called by BIOS when Control-Break is hit (vector was set up in Init).
; Simply notes that a break was hit.  Flag is checked during input calls.

break_handler   proc
        mov     cs:brkkey.len, 1
        iret
break_handler   endp


;------ badcmd -------------------------------------------------------
; Invalid function request by DOS.
badcmd  proc    near
        mov     ax, 813h                ; return "Error: invalid cmd"
        ret
badcmd  endp


;------ nopcmd -------------------------------------------------------
; Unimplemented or dummy function request by DOS.
nopcmd  proc    near
        xor     ax, ax                  ; No error, not busy.
        ret
nopcmd  endp

;------- dos function #4 -----------------------------------------------
; Reads CX characters from the keyboard, places them in buffer at ES:SI.
dosfn4  proc    near
        jcxz    dos4done
        mov     di,si
dos4lp: push    cx
        call    getchar
        pop     cx
        stosb
        loop    dos4lp
dos4done:
        xor     ax,ax                   ; No error, not busy.
        ret
dosfn4  endp

;-------- dos function #5: non-destructive input, no wait ------
; One-character lookahead into the keyboard buffer.
; If no characters in buffer, return BUSY; otherwise, get value of first
; character of buffer, stuff into request header, return DONE.
dosfn5  proc    near
        call    peekchar
        jz      dos5_busy
        lds     bx,req_ptr
        mov     [bx+0Dh], al
        xor     ax, ax                  ; No error, not busy.
        ret
dos5_busy:
        mov     ax, 200h                ; No error, busy.
        ret

dosfn5  endp

;-------- dos function #6: input status --------------------------
; Returns "busy" if no characters waiting to be read.
dosfn6  proc    near
        call    peekchar
        mov     ax, 200h                ; No error, busy.
        jz      dos6_exit
        xor     ax, ax                  ; No error, not busy.
dos6_exit:
        ret
dosfn6  endp

;-------- dos function #7: flush input buffer --------------------
; Clears the IBM keyboard input buffer.  Since it is a circular
; queue, we can do this without knowing the beginning and end
; of the buffer; all we need to do is set the tail of the queue
; equal to the head (as if we had read the entire queue contents).
; Also resets all the device driver's stuffahead buffers.
dosfn7  proc    near
        mov     ax, abs40
        mov     es, ax
        mov     ax, es:buffer_head      ; clear queue by making the tail
        mov     es:buffer_tail, ax      ; equal to the head
        xor     ax, ax                  ; no error, not busy
        mov     fnkey.len, ax           ; Reset the stuffahead buffers.
        mov     cprseq.len, ax
        mov     brkkey.len, ax
        mov     xlatseq.len, ax
        ret
dosfn7  endp

	if	takeBIOS
;--- new_vid_bios -------------------------------------------
; New_vid_bios simply replaces the write_tty call.
; All other calls get sent to the old video bios.
; This gives BIOS ANSI capability.
; However, it takes away the escape character.
; If this is not desired, just tell init to not take over the vector.

new_vid_bios	proc
	cmp	ah, 14
	jz	nvb_write_tty
	jmp	dword ptr cs:old_vid_bios
nvb_write_tty:
	push	cx
	mov	cl, cs:cur_attrib
	; If in graphics mode, BL is new color
;	call	in_g_mode		; returns carry set if text mode
;	jc	nvb_wt_text
	cmp	cs:video_mode, 4
	jb	nvb_wt_text
	cmp	cs:video_mode, 7
	jz	nvb_wt_text
		mov	cs:cur_attrib, bl	; ja?
nvb_wt_text:
	int	29h			; write AL
	mov	cs:cur_attrib, cl	; restore color
	pop	cx
	iret

new_vid_bios	endp
	endif

;------ int_29 ----------------------------------------------
; Int 29 handles DOS quick-access putchar.
; Last device loaded with attribute bit 4 set gets accessed for
; single-character writes via int 29h instead of via interrupt.
; Must preserve all registers.
; Installed as int 29h by dosfn0 (init).
int_29_buf      db      ?

int_29  proc    near
        sti
        push    ds
        push    es
        pushall

	 mov     bx, 40h
	 mov     ds, bx
	 assume  cs:code, ds:abs40
	 mov     bl, video_rows
	 mov     cs:max_y, bl
	 
	 mov     cx,1
        mov     bx,cs
        mov     es,bx
        mov     ds,bx
	 assume  cs:code, ds:code
        mov     si,offset int_29_buf
        mov     [si],al
        call    dosfn8
	 popall
        pop     es
        pop     ds
        iret
int_29  endp

;------ dosfn8 -------------------------------------------------------
; Handles writes to the device (with or without verify).
; Called with
;  CX    = number of bytes to write
;  ES:SI = transfer buffer
;  DS    = CS, so we can access local variables.

dosfn8  proc    near

        mov     f_cptr_seg, es  ; save segment of char ptr

        ; Read the BIOS buffer address/cursor position variables.
        mov     ax,abs40
        mov     ds,ax
        assume  ds:abs40

        ; Find current video mode and screen size.
        mov     ax,word ptr crt_mode    ; al = crt mode; ah = # of columns
        mov     cs:video_mode, al
        dec     ah                      ; ah = max column
        mov     cs:max_x, ah

        ; Find current cursor coordinates.
        mov     al,active_page
        cbw
        shl     ax,1
        mov     bx,ax
        mov     ax,cursor_posn[bx]
        mov     cs:cur_coords,AX

        ; Find video buffer segment address; adjust so ofs is 0; return in AX.
        ; DS is abs40.
        mov     ax,addr_6845            ; 6845 address
        mov     cs:port_6845,ax

        mov     ax,cs
        mov     ds,ax
        assume  ds:code

        mov     ax,0B000h               ; assume it's a monochrome card...
        cmp     video_mode,7
        jz      d8_gots
        mov     ah,0B8h                 ; but if not mode 7, it's color.
d8_gots:
        mov     video_seg,ax
        mov     es,ax
        call    xy_to_regs              ; Set DX, DI according to cur_coords.

        ; | If in graphics mode, clear old pseudocursor
        cmp     cs:video_mode, 4
        jb      d8_no_cp
        cmp     cs:video_mode, 7
        jz      d8_no_cp
        call    pseudocursor            ; write block in xor
d8_no_cp:
        mov     ah, cur_attrib
        mov     ds, f_cptr_seg          ; get segment of char ptr
        assume  ds:nothing
        cld                             ; make sure we'll increment

        ; Get a character, put it on the screen, repeat 'til end of line
        ; or no more characters.
        jcxz    f_loopdone              ; if count = 0, we're already done.
        cmp     cs:escvector, 0         ; If in middle of an escape sequence,
        jnz     f_in_escapex            ; jump to escape sequence handler.

f_tloop: ; If not in graphics mode, jump to alternate loop
         ; What a massive kludge!  A better approach would have been
         ; to collect characters for a "write n chars" routine
         ; which would handle both text and graphics modes.
        cmp     cs:video_mode,4
        jb      f_t_cloop
        cmp     cs:video_mode,7
        jz      f_t_cloop


f_g_cloop:
        lodsb                           ; get char! (al = ds:[si++])
        cmp     al,28                   ; is it a control char?
        jb      f_control               ;  maybe...
f_g_nctl:
        call    putchar
        dec     dx                      ; count down to end of line
        loopnz  f_g_cloop               ; and go back for more.
        jmp     short f_t_at_eol

f_t_cloop:
        lodsb                           ; get char! (al = ds:[si++])
        cmp     al,28                   ; is it a control char?
        jb      f_control               ;  maybe...
f_t_nctl:
        stosw                           ; Put Char! (es:[di++] = ax)
        dec     dx                      ; count down to end of line
        loopnz  f_t_cloop               ; and go back for more.
        jz      f_at_eol                ; at end of line; maybe do a crlf.
        jmp     short f_loopdone

f_looploop:
f_ansi_exit:                            ; in case we switched into
        loopnz  f_tloop                 ; a graphics mode
f_t_at_eol:
        jz      f_at_eol

f_loopdone:

        ;--------- All done with write request -----------
        ; DI is cursor address; cursor position in cur_y, dl.
        mov     ax, cs
        mov     ds, ax                  ; get our segment back
        assume  ds:code

        ; Restore cur_x = max_x - dx + 1.
        mov     al, max_x
        inc     al
        sub     al, dl
        mov     cur_x, al
        ; Set cursor position; cursor adr in DI; cursor pos in cur_x,cur_y
        call    set_pseudocursor
        ; Return to DOS.
        xor     ax, ax                  ; No error, not busy.
        ret

        ;---- handle control characters ----
        ; Note: cur_x is not kept updated in memory, but can be
        ; computed from max_x and dx.
        ; Cur_y is kept updated in memory.
f_control:
        cmp     al,13                   ; carriage return?
        jz      f_cr
        cmp     al,10                   ; line feed?
        jz      f_lf
        cmp     al,27                   ; Is it an escape?
        jz      f_escapex
        cmp     al,8                    ; backspace?
        jz      f_bs
        cmp     al,9                    ; tab?
        jz      f_tabx
        cmp     al,7                    ; bell?
        jz      f_bell
f_not_ansi:                             ; not a control char
        cmp     cs:video_mode, 4
        jb      f_t_nctl
        cmp     cs:video_mode, 7
        jz      f_t_nctl
        jmp     f_g_nctl

f_tabx: jmp     f_tab
f_escapex:
        jmp     f_escape
f_in_escapex:
        jmp     f_in_escape

f_bs:   ;----- Handle backspace -----------------
        ; Moves cursor back one space without erasing.  No wraparound.
        cmp     dl, cs:max_x            ; wrap around to previous line?
        ja      fbs_wrap                ; yep; disallow it.
        dec     di                      ; back up one char & attrib
        dec     di
        inc     dx                      ; and note one more char left on line.
fbs_wrap:
        jmp     f_looploop

f_bell: ;----- Handle bell ----------------------
        call    beep
        or      al, al                  ; clear z
        jmp     f_looploop              ; Let main loop decrement cx.

f_cr:   ;----- Handle carriage return -----------
        ; di -= cur_x<<1;               set di= address of start of line
        ; dx=max_x+1;                   set bx= chars left in line
        mov     al, cs:max_x
        cbw
        inc     ax
        sub     al,dl                   ; Get cur_x into ax.
        sub     di,ax
        sub     di,ax
        mov     dl,cs:max_x             ; Full line ahead of us.
        inc     dx
        mov     ah,cs:cur_attrib        ; restore current attribute
        or      al,1                    ; clear z
        jmp     f_looploop              ; and let main loop decrement cx

f_at_eol:
        ;----- Handle overrunning right end of screen -------
        ; cx++;                         compensate for double loop
        ; if (!wrap_flag) { dx++; di-=2; }
        ; else do_crlf;
        inc     cx
        test    cs:wrap_flag, 1
        jnz     feol_wrap
        dec     di
        dec     di
        inc     dx
        jmp     f_looploop
feol_wrap:
        ; dx=max_x+1;                   set bx= chars left in line
        ; di -= 2*(max_x+1);
        ; do_lf
        mov     dl, cs:max_x
        inc     dx
        sub     di, dx
        sub     di, dx
        ; fall thru to line feed routine

f_lf:   ;----- Handle line feed -----------------
        ; if (cur_y >= max_y) scroll;           scroll screen up if needed
        ; else { cur_y++; di += max_x<<1;       else increment Y

        mov     al,cs:Max_y
        cmp     cs:Cur_y,al             ; do we need to scroll screen?
        jae     flf_scroll              ;   yes, do it
        inc     cs:cur_y
        mov     al,cs:Max_x
        cbw
        inc     ax
        shl     ax,1
        add     di,ax
        mov     ah,cs:cur_attrib        ; restore current attribute
        or      al,1                    ; clear z
        jmp     f_looploop              ; and let main loop decrement cx
flf_scroll:
        push    ax
        push    bx
        push    cx
        push    dx
        call    get_blank_attrib
        mov     bh,ah                   ; color to use on new blank areas
        mov     dl,cs:max_x
        mov     dh,cs:max_y
        cmp     cs:video_mode,4
        jb      flf_scrollt
        cmp     cs:video_mode,7
        je      flf_scrollt
        mov     al,1                    ; AL is number of lines to scroll.
        mov     ah,6                    ; BIOS: scroll up
        xor     cx,cx
        int     10h                     ; call BIOS to scroll a rectangle.
flf_scroll_done:
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        mov     ah,cs:cur_attrib        ; restore current attribute
        or      al,1                    ; clear z
        jmp     f_looploop              ; and let main loop decrement cx
flf_scrollt:
        push    es
        push    ds
        push    si
        push    di
        mov     ax,abs40
        mov     ds,ax
        assume  ds:abs40
        mov     al,active_page
        cbw
        mov     cx,ax
        xor     di,di                   ; starting video loc
        mov     ax,0B000h               ; assume mono
        cmp     cs:video_mode,7
        je      flf_st_got
        mov     ah,0B8h                 ; it's color
        jcxz    flf_st_got
flf_st_getpage:
        add     di,4096                 ; move to active page
        loop    flf_st_getpage
flf_st_got:
        mov     ds,ax                   ; set DS and ES to video segment
        mov     es,ax
        mov     si,di
        mov     al,dh
        inc     dx
        mul     dl
        mov     cx,ax                   ; CX is number of chars to move
        xor     dh,dh
        add     si,dx
        add     si,dx                   ; SI points to one line below DI
        rep     movsw                   ; scroll it
        mov     cx,dx                   ; CX is chars per line
        mov     ah,bh                   ; attribute
        mov     al," "
        rep     stosw                   ; clear the bottom line
        pop     di
        pop     si
        pop     ds
        pop     es
        assume  ds:code
        jmp     flf_scroll_done


f_tab:  ;----- Handle tab expansion -------------
        ; Get cur_x into al.
        mov     al, cs:max_x
        inc     al
        sub     al, dl
        ; Calculate number of spaces to output.
        push    cx                      ; save cx
        mov     cl, al                  ; get zero based x coordinate
        and     cx,7
        neg     cl
        add     cl,8                    ; 0 -> 8, 1 -> 8, ... 7 -> 1
        sub     dx, cx                  ; update chars-to-eol, maybe set z
        pushf                           ; || save Z for main loop
        ; ah is still current attribute.  Move CX spaces to the screen.
        mov     al, ' '
        cmp     cs:video_mode, 4
        jb      F_SPC_MV
        cmp     cs:video_mode, 7
        jnz     f_tab_putc
F_SPC_MV:
        REP     STOSW
        popf                            ; || restore Z flag for main loop test
        pop     cx                      ; restore cx
        jmp     f_looploop              ; Let main loop decrement cx.

;--------------- graphics mode support -----------------------

f_tab_putc:     ; graphics mode- call putc to put the char
        add     dx, cx                  ; move back to start of tab
f_tp_lp:
        call    putchar
        dec     dx                      ; go to next cursor position
        loop    f_tp_lp
        popf                            ; Z set if wrapped around EOL
        pop     cx
        jmp     f_looploop


;---- putchar ------------------------------------------------
; Writes char AL, attribute AH to screen at (max_x+1-dl), cur_y.
; On entry, registers set up as per xy_to_regs.
; Preserves all registers.
putchar proc    near
        push    dx
        push    cx
        push    bx
        push    ax
        ; 1. Set cursor position.
        mov     al, cs:max_x
        inc     al
        sub     al,dl
        mov     cs:cur_x, al
        mov     dx, cs:cur_coords       ; get X & Y into DX
        xor     bx, bx                  ; choose dpy page 0
        mov     ah, 2                   ; chose "Set Cursor Position"
        int     10h                     ; call ROM BIOS
        ; 2. Write char & attribute.
        mov     cx,1
        pop     ax                      ; get char in AL
        push    ax
        mov     bl,ah                   ; attribute in BL
        xor     bh,bh
        mov     ah,9
        int     10h
        pop     ax
        pop     bx
        pop     cx
        pop     dx
        ret
putchar endp

;---- set_pseudocursor ------------
; If in graphics mode, set pseudocursor, else set real cursor.
; Destroys DS!!!!

set_pseudocursor        proc    near
        cmp     cs:video_mode, 4
        jb      SET_CURS
        cmp     cs:video_mode, 7
        jnz     pseudocursor

SET_CURS:    ; Write directly to 6845 cursor address register.
        mov     bx,di
        shr     bx,1                   ; convert word index to byte index

        mov     dx,Port_6845
        mov     al,0Eh
        out     dx,al

        jmp     $+2
        inc     dx
        mov     al, bh
        out     dx, al

        jmp     $+2
        dec     dx
        mov     al, 0fh
        out     dx, al

        jmp     $+2
        inc     dx
        mov     al, bl
        out     dx, al

        ; Set cursor position in low memory.
        assume  ds:abs40
        mov     ax, abs40
        mov     ds, ax
        mov     ax, cs:cur_coords
        mov     cursor_posn,ax
        ret

        assume  ds:code
set_pseudocursor        endp


;---- pseudocursor --------------------------------------------------
; Writes a color 15 block in XOR at the current cursor location.
; Preserves DS, ES, BX, CX, DX, SI, DI.
; Should be disableable- the pseudocursor slows down single-char
; writes by a factor of three.
pseudocursor    proc    near
        mov     ax, 8f16h       ; xor, color 15, ^V (small block)
        call    putchar
        ret
pseudocursor    endp

;--------------- end of graphics mode support --------------------

dosfn8  endp

;--- get_blank_attrib ------------------------------------------------
; Determine new attribute and character for a new blank region.
; Use current attribute, just disallow blink and underline.
; (Pretty strange way to do it.  Might want to disallow rev vid, too.)
; Returns result in AH, preserves all other registers.
get_blank_attrib        proc    near
        xor     ah,ah
        cmp     cs:video_mode, 4
        jb      GB_TX
        cmp     cs:video_mode, 7
        jnz     gb_aok                  ; if graphics mode, 0 is bkgnd
GB_TX:
        mov     ah, cs:cur_attrib
        and     ah,7fh                  ; disallow blink
        cmp     cs:video_mode,7         ; monochrome?
        jne     gb_aok
        cmp     ah,1                    ; underline?
        jne     gb_aok
        mov     ah,7                    ; yep- set it to normal.
gb_aok: ret
get_blank_attrib        endp


;---- searchbuf --------------------------------------------
; Called by getchar and peekchar to see if any characters are
; waiting to be gotten from sources other than BIOS.
; Returns with Z set if no chars found, BX=keybuf & SI=keybuf.len otherwise.
searchbuf       proc    near
        ; Search the stuffahead buffers.
        mov     cx,4                    ; number of buffers to check for chars
        mov     bx,offset fnkey -4
sbloop: add     bx,4                    ; point to next buffer record
        mov     si,[bx].len
        or      si,si                   ; empty?
        loopz   sbloop                  ; if so, loop.
        ret
searchbuf       endp

;---- getchar -----------------------------------------------
; Returns AL = next char.
; Trashes AX, BX, CX, BP, SI.
getchar proc    near
gc_searchbuf:
        ; See if any chars are waiting in stuffahead buffers.
        call    searchbuf
        jz      gc_trykbd               ; No chars?  Try the keyboard.
        ; A nonempty buffer was found.
        dec     [bx].len
        dec     si
        mov     bp, [bx].adr            ; get pointer to string
        mov     al, byte ptr ds:[bp][si]; get the char
        ; Recognize function key sequences, move them to highest priority
        ; queue.
        sub     si,1                    ; set carry if si=0
        jc      gc_nofnkey              ; no chars left -> nothing to protect.
        cmp     bx, offset fnkey
        jz      gc_nofnkey              ; already highest priority -> done.
        or      al,al
        jnz     gc_nofnkey              ; nonzero first byte -> not fnkey.
        ; Found a function key; move it to highest priority queue.
        dec     [bx].len
        mov     ah, byte ptr ds:[bp][si]; get the second byte of fn key code
gc_fnkey:
        mov     fnkey.len, 1
        mov     fnkeybuf, ah            ; save it.
gc_nofnkey:
        ret                             ; Valid char in AL.  Return with it.

gc_trykbd:
        xor     ah,ah
        int     16h                     ; BIOS returns with char in AX
        ; If it's Ctrl-break, it has already been taken care of.
        or      ax, ax
        jz      gc_trykbd

gcbark: or      al,al                   ; Is it a function key?
        jz      gc_fnkey                ;   yep, special treatment
gcdone: ret     ; with character in AL.
getchar endp

;---- peekchar -----------------------------------------------
; Returns Z if no character ready, AL=char otherwise.
; Trashes AX, BX, CX, BP, SI.
peekchar        proc    near
pc_searchbuf:
        call    searchbuf
        jz      pc_trykbd               ; No chars?  Try the keyboard.
        ; A nonempty buffer was found.
        dec     si
        mov     bp,[bx].adr             ; get pointer to string
        mov     al,byte ptr ds:[bp][si] ; get the char
        ; Valid char from buffer in AL.  Return with it.
        jmp     short pcdone

pc_brk: int     16h                     ; get rid of control-break in buffer

pc_trykbd:
        mov     ah,1
        int     16h                     ; BIOS returns with char in AX
        jz      pcexit
        or      ax,ax
        jz      pc_brk  ; If ctl-brk, it's already been taken care of- kill it.

pcdone: or      ah,1                    ; NZ; char ready!
pcexit: ret     ; with character in AL, Z true if no char waiting.
peekchar        endp

;---- beep ------------------------------------------------------
; Beep speaker; period given by beep_div, duration by beep_len.
; Preserves all registers.

beep_div        dw      1300            ; fairly close to IBM beep
beep_len        dw      3               ; 3/18 sec- shorter than IBM

beep    proc    near
	 pushall
        mov     al,10110110b            ; select 8253
        mov     dx,43h                  ; control port address
        out     dx,al
        dec     dx                      ; timer 2 address
        mov     ax, cs:beep_div
        out     dx,al                   ; low byte of divisor
        mov     al,ah
        out     dx,al                   ; high byte of divisor
        mov     dx,61h
        in      al,dx                   ; get current value of control bits
        push    ax
        or      al, 3
        out     dx,al                   ; turn speaker on

        ; Wait for desired duration by monitoring time-of-day 18 Hz clock
        push    es
        mov     ax,abs40
        mov     es,ax
        assume  es:abs40
        mov     bx,timer_low
        add     bx,cs:beep_len
        mov     cx, -1                  ; emergency, in case clock dead

beeplp: mov     ax, timer_low
        cmp     ax,bx
        loopne  beeplp

        pop     es
        assume  es:code
        pop     ax
        and     al,0FCh                 ; turn speaker off
        out     dx,al
	 popall
        ret
beep    endp

CODE    ends
        end
